//RayTracing.js
//This file is to include the code for ray tracing code

//Support Functions

function _sphere_norm(sphere, pt) {
    var result = new coord(pt.x-sphere.vertex.x,
			   pt.y-sphere.vertex.y,
			   pt.z-sphere.vertex.z );
    return vec_normalize(result);
}

function _pt_shading(pt, t_ray, norm, poly) {
    var result = new rgbcolor(0,0,0,1);
    if(poly.material == null) return result;
    var reflect_r = vec_normalize(_get_reflection(t_ray, norm));
    var dir = new coord(0,0,0);
    var Ka = poly.material.Ka;
    var Kd = poly.material.Kd;
    var Ks = poly.material.Ks;
    if(poly.text != null) {
	Ka = Kd = Ks = poly.text.get_color(pt);
	Kd.r /= 255;
	Kd.g /= 255;
	Kd.b /= 255;
    }
    //Ambient light
    if(render.lighting.ambient) {
	result.r += ambient_light.r*Ka.r;
	result.g += ambient_light.g*Ka.g;
	result.b += ambient_light.b*Ka.b;
    }
    //-------------
    if(render.lighting.specular || render.lighting.diffuse) {
	for(var k = 0; k < lights.length; ++k) {
	    //hit check
	    var light = lights[k];
	    dir.x = light.vertex.x - pt.x;
	    dir.y = light.vertex.y - pt.y;
	    dir.z = light.vertex.z - pt.z;
	    var thresh_t = vec_length(dir);
	    dir = vec_normalize(dir);
	    var temp_ray = new type_ray(pt, dir);
	    var hit_info = _hit_check(temp_ray);
	    
	    if(hit_info.hit == true && hit_info.t < thresh_t) {
		continue;
	    }
	    var tc = new rgbcolor(0,0,0,1);
	    //Diffuse light
	    if(render.lighting.diffuse) {
		var diff_cos = vec_dotp(norm, dir);
		tc.r += Kd.r*light.color.r*diff_cos;
		tc.g += Kd.g*light.color.g*diff_cos;
		tc.b += Kd.b*light.color.b*diff_cos;
	    }
	    //Specular light
	    if(render.lighting.specular) {
		var spec = Math.pow(vec_dotp(dir, reflect_r), poly.material.Sp);
		tc.r += Ks.r*spec*light.color.r;
		tc.g += Ks.g*spec*light.color.g;
		tc.b += Ks.b*spec*light.color.b;
	    }
	    result.add(tc);
	}
    }
    if(result.r > 255) result.r = 255;
    if(result.g > 255) result.g = 255;
    if(result.b > 255) result.b = 255;
    return result;
}

//Type: 0: tri, 1: sphere, 2: surface
function _get_refraction(t_ray, hit_pt, type, poly) {

    if(type == 1) { // Is is sphere
	var sphere = poly;
	//get refraction light direction
	var hit_norm = _sphere_norm(sphere, hit_pt);
	var temp_ray = new type_ray(hit_pt, hit_norm);
	temp_ray.norm = _get_refraction_direction(t_ray,hit_norm,
						  1,sphere.material.refr_idx);
	if(temp_ray.norm == null) return null;
	var temp_pt = _sphere_hit_far(temp_ray, sphere);
	var out_ray = new type_ray(temp_pt, temp_ray.norm);
	hit_norm = _sphere_norm(sphere, temp_pt);
	hit_norm.x *= -1;
	hit_norm.y *= -1;
	hit_norm.z *= -1;
	out_ray.norm = _get_refraction_direction(temp_ray,hit_norm,
						 sphere.material.refr_idx,
						 1);
	if(out_ray.norm == null) return null;
	return out_ray;
    }
    else if(type == 2) { //It is surface
	var plane = poly;
	var temp_norm = _get_refraction_direction(t_ray,plane.norm,1,plane.material.refr_idx);
	if(temp_norm == null) return null;
	var temp_len = -1*vec_dotp(plane.norm,temp_norm);
	var temp_t = plane.depth/temp_len;
	var out_pt = new coord(hit_pt.x, hit_pt.y, hit_pt.z);
	out_pt.x += temp_t*temp_ray.norm.x;
	out_pt.y += temp_t*temp_ray.norm.y;
	out_pt.z += temp_t*temp_ray.norm.z;
	return new type_ray(out_pt, new coord(t_ray.norm.x,t_ray.norm.y,t_ray.norm.z));
    }
    
};

function _sphere_hit_far(t_ray, sphere) {
    var cob = t_ray.norm.x*(t_ray.vertex.x-sphere.vertex.x)
	+t_ray.norm.y*(t_ray.vertex.y-sphere.vertex.y)
	+t_ray.norm.z*(t_ray.vertex.z-sphere.vertex.z);
    cob = cob*2;
    var coc = (t_ray.vertex.x-sphere.vertex.x)*(t_ray.vertex.x-sphere.vertex.x)
	+(t_ray.vertex.y-sphere.vertex.y)*(t_ray.vertex.y-sphere.vertex.y)
	+(t_ray.vertex.z-sphere.vertex.z)*(t_ray.vertex.z-sphere.vertex.z)
	-sphere.r*sphere.r;
    var temp = cob*cob - 4*coc;
    if(temp < 0.00000001) return null;
    var t1, t2;
    t1 = (-1*cob+Math.sqrt(temp)) / 2;
    t2 = (-1*cob-Math.sqrt(temp)) / 2;
    var res_t = (t1>t2)?t1:t2;
    var pt = new coord(t_ray.vertex.x+res_t*t_ray.norm.x,
		       t_ray.vertex.y+res_t*t_ray.norm.y,
		       t_ray.vertex.z+res_t*t_ray.norm.z);
    return pt;
}

function _xy_intri(t_ray, tri) {
    
};

function _trace_tri(t_ray) {
    var result = new Object();
    result.hit = false;
    return result;
};

function _trace_sphere(t_ray) {
    var result = new Object();
    result.hit = false;
    if(polygons.sphere.length == 0) {
	return result;
    }
    result.t = -1;
    for(var k = 0; k < polygons.sphere.length; ++k) {
	var sphere = polygons.sphere[k];
	var cob = t_ray.norm.x*(t_ray.vertex.x-sphere.vertex.x)
	    +t_ray.norm.y*(t_ray.vertex.y-sphere.vertex.y)
	    +t_ray.norm.z*(t_ray.vertex.z-sphere.vertex.z);
	cob = cob*2;
	var coc = (t_ray.vertex.x-sphere.vertex.x)*(t_ray.vertex.x-sphere.vertex.x)
	    +(t_ray.vertex.y-sphere.vertex.y)*(t_ray.vertex.y-sphere.vertex.y)
	    +(t_ray.vertex.z-sphere.vertex.z)*(t_ray.vertex.z-sphere.vertex.z)
	    -sphere.r*sphere.r;
	var temp = cob*cob - 4*coc;
	if(temp < 0) continue;
	var t1, t2;
	t1 = (-1*cob+Math.sqrt(temp)) / 2;
	t2 = (-1*cob-Math.sqrt(temp)) / 2;
	var res_t = (t1 > 0.0000001)?t1:-1;
	res_t = (t2>0.00000001 && t2<t1)?t2:res_t;
	if(res_t > 0.0000001 && (result.t < 0 || res_t < result.t)) {
	    result.hit = true;
	    result.t = res_t;
	    result.poly = sphere;
	    result.type = 1;
	}
    }
    return result;
};

function _trace_plane(t_ray) {
    var result = new Object();
    result.hit = false;
    if(polygons.plane.length == 0) {
	return result;
    }
    result.t = -1;
    for(var k = 0; k < polygons.plane.length; ++k) {
	var plane = polygons.plane[k];
	var temp_coord = new coord(t_ray.vertex.x,t_ray.vertex.y,t_ray.vertex.z);
	//temp_coord.add(plane.vertex);
	temp_coord.x -= plane.vertex.x;
	temp_coord.y -= plane.vertex.y;
	temp_coord.z -= plane.vertex.z;
	var bot = vec_dotp(plane.norm, t_ray.norm);
	if( bot < 0.00000001 && bot > -0.00000001 ) {
	    continue;
	}
	var res_t = -1*vec_dotp(plane.norm, temp_coord)/bot;
	if(plane.depth > 0 && vec_dotp(t_ray.norm, plane.norm) > 0) {
	    //Substract the thickness of plane
	    var minus_t = plane.depth/vec_dotp(t_ray.norm,norm);
	    res_t -= minus_t;
	}
	if(res_t <= 0.0000001) continue;
	var dist = new coord(t_ray.vertex.x+res_t*t_ray.norm.x - plane.vertex.x,
			     t_ray.vertex.y+res_t*t_ray.norm.y - plane.vertex.y,
			     t_ray.vertex.z+res_t*t_ray.norm.z - plane.vertex.z);
	if(plane.depth > 0 && vec_dotp(t_ray.norm, plane.norm) > 0) {
	    dist.x += plane.norm.x*plane.depth;
	    dist.y += plane.norm.y*plane.depth;
	    dist.z += plane.nrom.z*plane.depth;
	}
	var dotx = vec_dotp(dist,plane.ax);
	if(dotx < 0) continue;
	if(plane.xres > 0 && dot > plane.xres) {
	    continue;
	}
	var doty = vec_dotp(dist,plane.ay);
	if(doty < 0) continue;
	if(plane.yres > 0 && doty > plane.yres) {
	    continue;
	}
	if(res_t > 0.0001 && (result.t < 0 || res_t < result.t) ) {
	    result.hit = true;
	    result.t = res_t;
	    result.poly = plane;
	    result.type = 2;
	}
    }
    return result;
};

function _get_refraction_direction(t_ray, norm, n1, n2) {
    var result = new coord(1, 0, 0);
    /*if(pts_distance(ray.vertex, sphere.vertex) < sphere.r) {
    //inside the sphere
    n1 = n2;
    n2 = 1;
    norm.x = -1*norm.x;
    norm.y = -1*norm.y;
    norm.z = -1*norm.z;
    }*/

    var cosine = Math.abs(vec_dotp(t_ray.norm,norm));
    var ratio = n1/n2;
    if(n1>n2 && Math.sqrt(1-cosine*cosine) >= 1/ratio) {
	return null;
    }

    result.x = ratio*t_ray.norm.x;
    result.y = ratio*t_ray.norm.y;
    result.z = ratio*t_ray.norm.z;

    var coef = ratio*cosine;
    coef += Math.sqrt(1-ratio*ratio*(1-cosine*cosine));
    result.x -= coef*norm.x;
    result.y -= coef*norm.y;
    result.z -= coef*norm.z;
    return vec_normalize(result);
}

function _hit_check(t_ray) {
    //return hit info,
    //.hit: true|false - if hit
    //.t: variable t
    //.poly: the hit polygons
    //.type: type 0|1|2
    var temp = null;
    var hit_info = new Object();
    hit_info.hit = false;
    hit_info.t = -1;
    temp = _trace_tri(t_ray);
    if(temp.hit == true) {
	hit_info.hit = true;
	hit_info.t = temp.t;
	hit_info.type = 0;
	hit_info.poly = temp.poly;
    }
    temp = _trace_sphere(t_ray);
    if(temp.hit == true) {
	if(hit_info.t < 0 || hit_info.t > temp.t) {
	    hit_info.hit = true;
	    hit_info.t = temp.t;
	    hit_info.type = 1;
	    hit_info.poly = temp.poly;
	}
    }
    temp = _trace_plane(t_ray);
    if(temp.hit == true) {
	if(hit_info.t < 0 || hit_info.t > temp.t) {
	    hit_info.hit = true;
	    hit_info.t = temp.t;
	    hit_info.type = 2;
	    hit_info.poly = temp.poly;
	}
    }
    return hit_info;
}

function _poly_norm(poly, type, pt) {
    if(type == 1) {
	return _sphere_norm(poly, pt);
    }
    else if(type == 2) {
	var ret = new coord(poly.norm.x, poly.norm.y, poly.norm.z);
	var dist = new coord(pt.x-poly.vertex.x, pt.y-poly.vertex.y, pt.z-poly.vertex.z);
	if(vec_dotp(dist, poly.norm) < -0.0000001) {
	    ret.x *= -1;
	    ret.y *= -1;
	    ret.z *= -1;
	}
	return ret;
    }
};

function _get_reflection(t_ray, norm) {
    var reflect_r = new coord(t_ray.norm.x, t_ray.norm.y, t_ray.norm.z);
    var product = vec_dotp(t_ray.norm, norm);
    reflect_r.x -= 2*product*norm.x;
    reflect_r.y -= 2*product*norm.y;
    reflect_r.z -= 2*product*norm.z;
    return reflect_r;
}

//Main Tracer
function ray_tracer( t_ray, level ) {
    var hit_info = _hit_check(t_ray);
    if(hit_info.hit == false) {
	//Hit things
	return display.bgcolor;
    }
    var next_pt = new coord(t_ray.vertex.x, t_ray.vertex.y, t_ray.vertex.z);
    next_pt.x += hit_info.t * t_ray.norm.x;
    next_pt.y += hit_info.t * t_ray.norm.y;
    next_pt.z += hit_info.t * t_ray.norm.z;

    var pt_color = null;
    var temp_norm = _poly_norm(hit_info.poly, hit_info.type, next_pt);
    pt_color = _pt_shading(next_pt, t_ray, temp_norm, hit_info.poly);
    if(level == 0 && hit_info.poly.material.transparent == false) {
	return pt_color;
    }
    if(hit_info.poly.material.transparent) {
	var out_ray = _get_refraction(t_ray, next_pt, hit_info.type, hit_info.poly);
	if(out_ray != null) {
	    var temp_color = null;
	    if(level > 0)
		temp_color = ray_tracer(out_ray, level-1);
	    else temp_color = ray_tracer(out_ray, 0);
	    var ratio = hit_info.poly.material.opaque;
	    pt_color.r = ratio*pt_color.r + (1-ratio)*temp_color.r;
	    pt_color.g = ratio*pt_color.g + (1-ratio)*temp_color.g;
	    pt_color.b = ratio*pt_color.b + (1-ratio)*temp_color.b;
	}
    }
    if(level == 0 || hit_info.poly.material.ref == false) {
	return pt_color;
    }
    var refl_r = _get_reflection(t_ray, temp_norm);
    var next_ray = new type_ray(next_pt, vec_normalize(refl_r));
    var recursive_color = ray_tracer(next_ray, level-1);
    var Ks = hit_info.poly.material.Ks;
    if(Ks == null) return pt_color;
    pt_color.r = Ks.r*recursive_color.r + (1-Ks.r)*pt_color.r;
    pt_color.g = Ks.g*recursive_color.g + (1-Ks.g)*pt_color.g;
    pt_color.b = Ks.b*recursive_color.b + (1-Ks.b)*pt_color.b;
    return pt_color;
};